1 // TODO fillStyle for lineSegment? 2 // TODO lineOffset for flow maps? 3 4 pv.SvgScene.line = function(scenes) { 5 var e = scenes.$g.firstChild; 6 if (scenes.length < 2) return e; 7 var s = scenes[0]; 8 9 /* segmented */ 10 if (s.segmented) return this.lineSegment(scenes); 11 12 /* visible */ 13 if (!s.visible) return e; 14 var fill = pv.color(s.fillStyle), stroke = pv.color(s.strokeStyle); 15 if (!fill.opacity && !stroke.opacity) return e; 16 17 /* points */ 18 var p = ""; 19 for (var i = 0; i < scenes.length; i++) { 20 var si = scenes[i]; 21 p += si.left + "," + si.top + " "; 22 23 /* interpolate (assume linear by default) */ 24 if (i < scenes.length - 1) { 25 var sj = scenes[i + 1]; 26 switch (s.interpolate) { 27 case "step-before": { 28 p += si.left + "," + sj.top + " "; 29 break; 30 } 31 case "step-after": { 32 p += sj.left + "," + si.top + " "; 33 break; 34 } 35 } 36 } 37 } 38 39 40 e = this.expect("polyline", e); 41 e.setAttribute("cursor", s.cursor); 42 e.setAttribute("points", p); 43 e.setAttribute("fill", fill.color); 44 e.setAttribute("fill-opacity", fill.opacity); 45 e.setAttribute("stroke", stroke.color); 46 e.setAttribute("stroke-opacity", stroke.opacity); 47 e.setAttribute("stroke-width", s.lineWidth); 48 return this.append(e, scenes, 0); 49 }; 50 51 pv.SvgScene.lineSegment = function(scenes) { 52 var e = scenes.$g.firstChild; 53 for (var i = 0, n = scenes.length - 1; i < n; i++) { 54 var s1 = scenes[i], s2 = scenes[i + 1]; 55 56 /* visible */ 57 if (!s1.visible || !s2.visible) continue; 58 var stroke = pv.color(s1.strokeStyle); 59 if (!stroke.opacity) continue; 60 61 /* Line-line intersection, per Akenine-Moller 16.16.1. */ 62 function intersect(o1, d1, o2, d2) { 63 return o1.plus(d1.times(o2.minus(o1).dot(d2.perp()) / d1.dot(d2.perp()))); 64 } 65 66 /* 67 * P1-P2 is the current line segment. V is a vector that is perpendicular to 68 * the line segment, and has length lineWidth / 2. ABCD forms the initial 69 * bounding box of the line segment (i.e., the line segment if we were to do 70 * no joins). 71 */ 72 var p1 = pv.vector(s1.left, s1.top), 73 p2 = pv.vector(s2.left, s2.top), 74 p = p2.minus(p1), 75 v = p.perp().norm(), 76 w = v.times(s1.lineWidth / 2), 77 a = p1.plus(w), 78 b = p2.plus(w), 79 c = p2.minus(w), 80 d = p1.minus(w); 81 82 /* 83 * Start join. P0 is the previous line segment's start point. We define the 84 * cutting plane as the average of the vector perpendicular to P0-P1, and 85 * the vector perpendicular to P1-P2. This insures that the cross-section of 86 * the line on the cutting plane is equal if the line-width is unchanged. 87 * Note that we don't implement miter limits, so these can get wild. 88 */ 89 if (i > 0) { 90 var s0 = scenes[i - 1]; 91 if (s0.visible) { 92 var v1 = p1.minus(s0.left, s0.top).perp().norm().plus(v); 93 d = intersect(p1, v1, d, p); 94 a = intersect(p1, v1, a, p); 95 } 96 } 97 98 /* Similarly, for end join. */ 99 if (i < (n - 1)) { 100 var s3 = scenes[i + 2]; 101 if (s3.visible) { 102 var v2 = pv.vector(s3.left, s3.top).minus(p2).perp().norm().plus(v); 103 c = intersect(p2, v2, c, p); 104 b = intersect(p2, v2, b, p); 105 } 106 } 107 108 /* points */ 109 var p = a.x + "," + a.y + " " 110 + b.x + "," + b.y + " " 111 + c.x + "," + c.y + " " 112 + d.x + "," + d.y; 113 114 e = this.expect("polygon", e); 115 e.setAttribute("cursor", s1.cursor); 116 e.setAttribute("points", p); 117 e.setAttribute("fill", stroke.color); 118 e.setAttribute("fill-opacity", stroke.opacity); 119 e = this.append(e, scenes, i); 120 } 121 return e; 122 }; 123